So far we have studied about various mophological operations and different thresholding techniques in some detail. Now it's time to apply these concepts for a practical application - Coin Detection.
In this assignment, you will work with 2 different images (so 2 different parts) and will use only morphological operations and thresholding techniques to detect the total number of coins present in the image. Your submission will be graded based on your use of the concepts covered in this module, experimentation performed to achieve at your final solution, documentation, and finally, the total number of coins successfully detected in the images. Each part will be of 15 marks. This assignment will be entirely manually graded so make sure that you do NOT remove any experimentation you have done as well as the observation you made after each step.
Proper documentation for each step should be provided with help of markdown
The main steps that you can follow to solve this assignment are:
We have also provided the results we obtained at the intermediate steps for your reference.
import cv2
import matplotlib.pyplot as plt
from dataPath import DATA_PATH
import numpy as np
%matplotlib inline
import matplotlib
matplotlib.rcParams['figure.figsize'] = (10.0, 10.0)
matplotlib.rcParams['image.cmap'] = 'gray'
# Enable intellisense
%config IPCompleter.greedy=True
# Image path
imagePath = DATA_PATH + "images/CoinsA.png"
# Read image
# Store it in the variable image
###
image = cv2.imread(imagePath)
###
imageCopy = image.copy()
plt.imshow(image[:,:,::-1]);
plt.title("Original Image")
# Convert image to grayscale
# Store it in the variable imageGray
###
imageGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
###
plt.figure(figsize=(12,12))
plt.subplot(121)
plt.imshow(image[:,:,::-1]);
plt.title("Original Image")
plt.subplot(122)
plt.imshow(imageGray);
plt.title("Grayscale Image");
# Split cell into channels
# Store them in variables imageB, imageG, imageR
###
imageR = image[:,:,2]
imageG = image[:,:,1]
imageB = image[:,:,0]
###
plt.figure(figsize=(20,12))
plt.subplot(141)
plt.imshow(image[:,:,::-1]);
plt.title("Original Image")
plt.subplot(142)
plt.imshow(imageB);
plt.title("Blue Channel")
plt.subplot(143)
plt.imshow(imageG);
plt.title("Green Channel")
plt.subplot(144)
plt.imshow(imageR);
plt.title("Red Channel");
You will have to carry out this step with different threshold values to see which one suits you the most. Do not remove those intermediate images and make sure to document your findings.
###
# numpy masking
maskB = image[...,0] < 25
maskG = image[...,1] < 25
maskR = np.ones(imageR.shape)
mask = np.bitwise_and(maskB, maskG)
imgcopy = image.copy()
imgcopy[mask] = 0
# what is mask type, shape, etc?
print(maskB.dtype, maskB.shape, np.ma.masked_array(imageB, mask=maskB).shape, np.ma.masked_array(imageB, mask=maskB).dtype)
# what does an applied mask look like?
plt.subplot(131)
plt.imshow(np.ma.masked_array(imageB, mask=mask))
# what does the mask look like
plt.subplot(132)
plt.imshow(mask)
plt.subplot(133)
plt.imshow(imgcopy[:,:,::-1])
###
###
# openCV thresholding
# THRESH_BINARY -> greater than
# THRESH_BINARY_INV -> less than
thb, imBth = cv2.threshold(imageB, 75 ,255, cv2.THRESH_BINARY)
thg, imGth = cv2.threshold(imageG, 25, 255, cv2.THRESH_BINARY)
thr, imRth = cv2.threshold(imageR, 30, 255, cv2.THRESH_BINARY_INV)
'''
maximize the circle of the coins and minimize the background using combined masks from
the RGB image then perform a binary not
'''
immax = cv2.max(imBth, imGth)
plt.subplot(141)
plt.imshow(immax)
immin = cv2.min(imBth, imGth)
plt.subplot(142)
plt.imshow(immin)
immax = cv2.max(immax, immin)
plt.subplot(143)
plt.imshow(immax)
immax = cv2.max(immax, imRth)
plt.subplot(144)
plt.imshow(immax)
###
# Display the thresholded image
###
print('my final output')
max_not = cv2.bitwise_not(immax)
plt.imshow(max_not)
###
You will have to carry out this step with different kernel size, kernel shape and morphological operations to see which one (or more) suits you the most. Do not remove those intermediate images and make sure to document your findings.
###
# try multiple kernels
m_3x3cross = cv2.getStructuringElement(cv2.MORPH_CROSS ,(3,3))
m_3x3rect = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
m_4x4ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE ,(4,4))
m_4x4rect = cv2.getStructuringElement(cv2.MORPH_RECT, (4,4))
m_5x5ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE ,(5,5))
m_5x5rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
###
###
# find the best image in the least amount of iterations
i = 3
d_3x3cross = cv2.dilate(max_not, m_3x3cross, iterations = i)
d_3x3rect = cv2.dilate(max_not, m_3x3rect, iterations = i)
d_4x4ellipse = cv2.dilate(max_not, m_4x4ellipse, iterations = i)
d_4x4rect = cv2.dilate(max_not, m_4x4rect, iterations = i)
d_5x5ellipse = cv2.dilate(max_not, m_5x5ellipse, iterations = i)
d_5x5rect = cv2.dilate(max_not, m_5x5rect, iterations = i)
###
plt.subplot(231)
plt.imshow(d_3x3cross)
plt.subplot(232)
plt.imshow(d_3x3rect)
plt.subplot(233)
plt.imshow(d_4x4ellipse)
plt.subplot(234)
plt.imshow(d_4x4rect)
plt.subplot(235)
plt.imshow(d_5x5ellipse)
plt.subplot(236)
plt.imshow(d_5x5rect)
print('my final output')
choice = d_4x4rect
plt.imshow(choice)
# Display all the images
# you have obtained in the intermediate steps
###
### YOUR CODE HERE
###
# Get structuring element/kernel which will be used for dilation
###
# Reise the kernels
m_3x3cross = cv2.getStructuringElement(cv2.MORPH_CROSS ,(3,3))
m_3x3rect = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
m_4x4ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE ,(4,4))
m_4x4rect = cv2.getStructuringElement(cv2.MORPH_RECT, (4,4))
m_5x5ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE ,(5,5))
m_5x5rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
###
###
# find the best image in the least amount of iterations
i = 2
e_3x3cross = cv2.erode(choice, m_3x3cross, iterations = i)
e_3x3rect = cv2.erode(choice, m_3x3rect, iterations = i)
e_4x4ellipse = cv2.erode(choice, m_4x4ellipse, iterations = i)
e_4x4rect = cv2.erode(choice, m_4x4rect, iterations = i)
e_5x5ellipse = cv2.erode(choice, m_5x5ellipse, iterations = i)
e_5x5rect = cv2.erode(choice, m_5x5rect, iterations = i)
###
# look for the best one
plt.subplot(321)
plt.imshow(e_3x3cross)
plt.subplot(322)
plt.imshow(e_3x3rect)
plt.subplot(323)
plt.imshow(e_4x4ellipse)
plt.subplot(324)
plt.imshow(e_4x4rect)
plt.subplot(325)
plt.imshow(e_5x5ellipse)
plt.subplot(326)
plt.imshow(e_5x5rect)
# they're all essentially good, personal choice I guess
choice2 = e_3x3cross
plt.imshow(choice2)
# using morphologyEx instead with rect kernels
# since the image I obtained is quite good, it only needs to patch the small holes
# this can be easily done using morphological close
i = 5 # iterations
c_3x3 = cv2.morphologyEx(max_not, cv2.MORPH_CLOSE, m_3x3rect, iterations = i)
c_4x4 = cv2.morphologyEx(max_not, cv2.MORPH_CLOSE, m_4x4rect, iterations = i)
c_5x5 = cv2.morphologyEx(max_not, cv2.MORPH_CLOSE, m_5x5rect, iterations = i)
plt.subplot(131)
plt.imshow(c_3x3)
plt.subplot(132)
plt.imshow(c_4x4)
plt.subplot(133)
plt.imshow(c_5x5)
###
### YOUR CODE HERE
###
# Set up the SimpleBlobdetector with default parameters.
params = cv2.SimpleBlobDetector_Params()
params.blobColor = 0
params.minDistBetweenBlobs = 2
# Filter by Area.
params.filterByArea = False
# Filter by Circularity
params.filterByCircularity = True
params.minCircularity = 0.8
# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0.8
# Filter by Inertia
params.filterByInertia =True
params.minInertiaRatio = 0.8
# Create SimpleBlobDetector
detector = cv2.SimpleBlobDetector_create(params)
Use detector.detect(image) to detect the blobs (coins). The output of the function is a list of keypoints where each keypoint is unique for each blob.
Print the number of coins detected as well.
# Detect blobs
###
out = detector.detect(choice2)
out_c3x3 = detector.detect(c_3x3)
out_c4x4 = detector.detect(c_4x4)
out_c5x5 = detector.detect(c_5x5)
###
# Print number of coins detected
###
print('There are {} coins in the image from dilate -> erosion'.format(len(out)))
print('There are {} coins in the image from morphologyEx 3x3'.format(len(out_c3x3)))
print('There are {} coins in the image from morphologyEx 4x4'.format(len(out_c4x4)))
print('There are {} coins in the image from morphologyEx 5x5'.format(len(out_c5x5)))
# seems the 4x4 rect dilate and 3x3 cross erode did a better job at keeping the coin borders clean
# than performing the morphologyEx
###
Note that we were able to detect all 9 coins. So, that's your benchmark.
Make sure to mark the center of the blobs as well. Use only the functions discussed in Image Annotation section in Week 1
You can extract the coordinates of the center and the diameter of a blob using k.pt and k.size where k is a keypoint.
# Mark coins using image annotation concepts we have studied so far
###
image2 = image.copy()
contours, hierarchy = cv2.findContours(choice2, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image2, contours[:len(contours)-1], -1, (0,255,0), 3);
for k in out:
x, y = k.pt
size = k.size
cv2.circle(image2, (int(x),int(y)), 10, (255,0,0), -1)
###
plt.imshow(image2[:,:,::-1])
# Display the final image
###
### YOUR CODE HERE
###
In the final step, perform Connected Component Analysis (CCA) on the binary image to find out the number of connected components. Do you think we can use CCA to calculate number of coins? Why/why not?
def displayConnectedComponents(im):
imLabels = im
# The following line finds the min and max pixel values
# and their locations in an image.
(minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(imLabels)
# Normalize the image so the min value is 0 and max value is 255.
imLabels = 255 * (imLabels - minVal)/(maxVal-minVal)
# Convert image to 8-bits unsigned type
imLabels = np.uint8(imLabels)
# Apply a color map
imColorMap = cv2.applyColorMap(imLabels, cv2.COLORMAP_JET)
# Display colormapped labels
plt.imshow(imColorMap[:,:,::-1])
# Find connected components
###
choice_not = cv2.bitwise_not(choice2)
_, coinlabels = cv2.connectedComponents(choice_not)
###
# Print number of connected components detected
###
print(coinlabels.max())
# Yes CCA can find the number of coins if you can segment each coin properly
###
# Display connected components using displayConnectedComponents
displayConnectedComponents(coinlabels)
# Display connected components using displayConnectedComponents
# function
###
### YOUR CODE HERE
###
In the final step, perform Contour Detection on the binary image to find out the number of coins.
# Find all contours in the image
###
# Find all contours in the image
contours, hierarchy = cv2.findContours(choice2, cv2.RETR_LIST, cv2.CHAIN_APPROX_TC89_L1 )
###
# Print the number of contours found
###
print(len(contours))
###
# Draw all contours
###
img = image.copy()
cv2.drawContours(img, contours, -1, (0,255,0), 3);
plt.imshow(img[:,:,::-1])
###
Let's only consider the outer contours.
help(cv2.rectangle)
# Seems like its not drawing the borders completely so, I'll try using cv2.rectangle
# contours, hierarchy = cv2.findContours(choice2, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
img = image.copy()
cv2.drawContours(img, contours[-1], -1, (0,255,0), 15);
plt.imshow(img[:,:,::-1])
###
pointa = (contours[-1][0,0,0], contours[-1][0,0,1])
pointb = (contours[-1][2,0,0], contours[-1][2,0,1])
cv2.rectangle(img, pointa, pointb, (0,255,0), thickness = 15);
plt.imshow(img[:,:,::-1])
###
for index,cnt in enumerate(contours):
area = cv2.contourArea(cnt)
perimeter = cv2.arcLength(cnt, True)
print("Contour #{} has area = {} and perimeter = {}".format(index+1,area,perimeter))
So, we only need the inner contours. The easiest way to do that will be to remove the outer contour using area.
# Print area and perimeter of all contours
###
### YOUR CODE HERE
###
# Print maximum area of contour
# This will be the box that we want to remove
###
area = cv2.contourArea(contours[-1])
print('Maximum area of contour = ' + str(area))
###
# Print maximum area of contour
# This will be the box that we want to remove
###
img = image.copy()
contours_copy = contours.copy()
for index,cnt in enumerate(contours_copy):
a = cv2.contourArea(cnt)
if area == a:
contours_copy = np.delete(contours_copy, index)
cv2.drawContours(img, contours_copy, -1, (0,255,0), 5);
plt.imshow(img[:,:,::-1])
###
# Remove this contour and plot others
###
### YOUR CODE HERE
###
for index,cnt in enumerate(contours_copy):
M = cv2.moments(cnt)
x = int(round(M["m10"]/M["m00"]))
y = int(round(M["m01"]/M["m00"]))
# Mark the center
cv2.circle(img, (x,y), 10, (0,0,255), -1);
plt.imshow(img[:,:,::-1])
# Fit circles on coins
###
### YOUR CODE HERE
###
Follow the same steps as provided in Assignment Part - A
# Image path
imagePath = DATA_PATH + "images/CoinsB.png"
# Read image
# Store it in variable image
###
image = cv2.imread(imagePath)
###
plt.imshow(image[:,:,::-1]);
plt.title("Original Image")
# Convert to grayscale
# Store in variable imageGray
###
imageGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
###
plt.figure(figsize=(12,12))
plt.subplot(121)
plt.imshow(image[:,:,::-1]);
plt.title("Original Image")
plt.subplot(122)
plt.imshow(imageGray);
plt.title("Grayscale Image");
# Split cell into channels
# Variables are: imageB, imageG, imageR
###
imageR = image[:,:,2]
imageG = image[:,:,1]
imageB = image[:,:,0]
###
plt.figure(figsize=(20,12))
plt.subplot(141)
plt.imshow(image[:,:,::-1]);
plt.title("Original Image")
plt.subplot(142)
plt.imshow(imageB);
plt.title("Blue Channel")
plt.subplot(143)
plt.imshow(imageG);
plt.title("Green Channel")
plt.subplot(144)
plt.imshow(imageR);
plt.title("Red Channel");
You will have to carry out this step with different threshold values to see which one suits you the most. Do not remove those intermediate images and make sure to document your findings.
help(cv2.threshold)
###
thr, imRth = cv2.threshold(imageR, 175 ,255, cv2.THRESH_BINARY)
thg, imGth = cv2.threshold(imageG, 90 ,255, cv2.THRESH_BINARY)
thb, imBth = cv2.threshold(imageB, 124 ,255, cv2.THRESH_BINARY)
###
plt.subplot(141)
plt.imshow(imRth)
plt.subplot(142)
plt.imshow(imGth)
plt.subplot(143)
plt.imshow(imBth)
immax = cv2.bitwise_and(imRth, imBth)
plt.subplot(121)
plt.imshow(immax)
immax = cv2.bitwise_or(immax, imBth)
immax = cv2.bitwise_and(immax, imBth)
plt.subplot(111)
plt.imshow(immax)
# Display image using matplotlib
###
### YOUR CODE HERE
###
You will have to carry out this step with different kernel size, kernel shape and morphological operations to see which one (or more) suits you the most. Do not remove those intermediate images and make sure to document your findings.
###
# get kernels
m_3x3cross = cv2.getStructuringElement(cv2.MORPH_CROSS ,(3,3))
m_3x3rect = cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
m_4x4ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE ,(4,4))
m_4x4rect = cv2.getStructuringElement(cv2.MORPH_RECT, (4,4))
m_5x5ellipse = cv2.getStructuringElement(cv2.MORPH_ELLIPSE ,(5,5))
m_5x5rect = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
###
###
# Remove white holes
imcopy = immax.copy()
i = 5
# c_3x3 = cv2.morphologyEx(imcopy, cv2.MORPH_CLOSE, m_3x3rect, iterations = i)
o_5x5 = cv2.morphologyEx(imcopy, cv2.MORPH_OPEN, m_5x5rect, iterations = i)
# plt.imshow(c_3x3)
plt.imshow(o_5x5)
###
###
### YOUR CODE HERE
###
###
# remove black holes
c_5x5 = o_5x5.copy()
i = 5
# c_3x3 = cv2.morphologyEx(imcopy, cv2.MORPH_CLOSE, m_3x3rect, iterations = i)
c_5x5 = cv2.morphologyEx(c_5x5, cv2.MORPH_CLOSE, m_5x5rect, iterations = i)
# plt.imshow(c_3x3)
plt.imshow(c_5x5)
###
c_5x5_2 = c_5x5.copy()
i = 45
c_5x5_2 = cv2.morphologyEx(c_5x5_2, cv2.MORPH_OPEN, m_5x5rect, iterations = i)
# too much
plt.imshow(c_5x5_2)
###
# remove coin centers
c_5x5_2 = c_5x5.copy()
i = 25
c_5x5_2 = cv2.morphologyEx(c_5x5_2, cv2.MORPH_OPEN, m_5x5rect, iterations = i)
plt.imshow(c_5x5_2)
###
i = 32
c_5x5_2 = cv2.erode(c_5x5_2, m_5x5rect, iterations = i)
plt.imshow(c_5x5_2)
i = 32
c_5x5_2 = cv2.dilate(c_5x5_2, m_5x5ellipse, iterations = i)
plt.imshow(c_5x5_2)
i = 32
c_5x5_2 = cv2.erode(c_5x5_2, m_5x5rect, iterations = i)
plt.imshow(c_5x5_2)
i = 32
c_5x5_2 = cv2.dilate(c_5x5_2, m_5x5ellipse, iterations = i)
plt.imshow(c_5x5_2)
i = 32
c_5x5_2 = cv2.erode(c_5x5_2, m_5x5rect, iterations = i)
plt.imshow(c_5x5_2)
i = 32
c_5x5_2 = cv2.dilate(c_5x5_2, m_5x5ellipse, iterations = i)
plt.imshow(c_5x5_2)
c_5x5_2_not = c_5x5_2.copy()
c_5x5_2_not = cv2.bitwise_not(c_5x5_2_not)
plt.imshow(c_5x5_2_not)
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
# Set up the SimpleBlobdetector with default parameters.
params = cv2.SimpleBlobDetector_Params()
params.blobColor = 0
params.minDistBetweenBlobs = 2
# Filter by Area.
params.filterByArea = False
# Filter by Circularity
params.filterByCircularity = True
params.minCircularity = 0.8
# Filter by Convexity
params.filterByConvexity = True
params.minConvexity = 0.8
# Filter by Inertia
params.filterByInertia =True
params.minInertiaRatio = 0.8
# Create SimpleBlobDetector
detector = cv2.SimpleBlobDetector_create(params)
Use detector.detect(image) to detect the blobs (coins). The output of the function is a list of keypoints where each keypoint is unique for each blob.
Print the number of coins detected as well.
###
out = detector.detect(c_5x5_2)
###
print('There are {} number of coins detected'.format(len(out)))
# Detect blobs
###
### YOUR CODE HERE
###
Make sure to mark the center of the blobs as well. Use only the functions discussed in Image Annotation section in Week 1
You can extract the coordinates of the center and the diameter of a blob using k.pt and k.size where k is a keypoint.
###
image2 = image.copy()
contours, hierarchy = cv2.findContours(c_5x5_2, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image2, contours[:len(contours)-1], -1, (0,0,255), 15);
for k in out:
x, y = k.pt
size = k.size
cv2.circle(image2, (int(x),int(y)), 15, (0,0,255), -1)
###
###
plt.imshow(image2[:,:,::-1])
###
###
### YOUR CODE HERE
###
Note that we were able to detect 8 coins. So, that's your benchmark.
Now, let's perform Connected Component Analysis (CCA) on the binary image to find out the number of connected components. Do you think we can use CCA to calculate number of coins? Why/why not?
###
_, coinlabels = cv2.connectedComponents(c_5x5_2_not)
print('Number of connected components detected = {}'.format(coinlabels.max()))
###
###
### YOUR CODE HERE
###
###
### YOUR CODE HERE
###
In the final step, perform Contour Detection on the binary image to find out the number of coins.
contours, hierarchy = cv2.findContours(c_5x5_2, cv2.RETR_LIST, cv2.CHAIN_APPROX_TC89_L1 )
img = image.copy()
cv2.drawContours(img, contours, -1, (0,255,0), 15);
plt.imshow(img[:,:,::-1])
print('Number of contours found = ' + str(len(contours)))
img = image.copy()
pointa = (contours[-1][0,0,0], contours[-1][0,0,1])
pointb = (contours[-1][2,0,0], contours[-1][2,0,1])
cv2.rectangle(img, pointa, pointb, (0,255,0), thickness = 35);
plt.imshow(img[:,:,::-1])
for index,cnt in enumerate(contours):
area = cv2.contourArea(cnt)
perimeter = cv2.arcLength(cnt, True)
print("Contour #{} has area = {} and perimeter = {}".format(index+1,area,perimeter))
area = cv2.contourArea(contours[-1])
print('Maximum area of contour = ' + str(area))
img = image.copy()
contours_copy = contours.copy()
for index,cnt in enumerate(contours_copy):
a = cv2.contourArea(cnt)
if area == a:
contours_copy = np.delete(contours_copy, index)
cv2.drawContours(img, contours_copy, -1, (0,255,0), 15);
plt.imshow(img[:,:,::-1])
img = image.copy()
cv2.drawContours(img, contours_copy, -1, (0,255,0), 15);
plt.imshow(img[:,:,::-1])
# print sorted area
area_lst = []
for cnt in contours_copy:
a = cv2.contourArea(cnt)
area_lst.append(a)
sorted(area_lst)
for a in area_lst:
print(a)
img = image.copy()
cv2.drawContours(img, contours_copy, -1, (0,255,0), 15);
index = 0
for k in out:
index += 1
x, y = k.pt
size = k.size
cv2.circle(img, (int(x),int(y)), 15, (0,0,255), -1)
cv2.putText(img, "{}".format(index), ((int(x+40)), (int(y-10))), cv2.FONT_HERSHEY_SIMPLEX, 5, (0, 0, 255),5);
plt.imshow(img[:,:,::-1])
# can't fit the circles to the coin
# Print the number of contours found
###
### YOUR CODE HERE
###
# Draw all contours
###
### YOUR CODE HERE
###
# Remove the inner contours
# Display the result
###
### YOUR CODE HERE
###
What do you think went wrong? As we can see, the outer box was detected as a contour and with respect to it, all other contours are internal and that's why they were not detected. How do we remove that? Let's see if we can use area of contours here.
# Print area and perimeter of all contours
###
### YOUR CODE HERE
###
# Print maximum area of contour
# This will be the box that we want to remove
###
### YOUR CODE HERE
###
# Remove this contour and plot others
###
### YOUR CODE HERE
###
Now, we have to remove the internal contours. Again here we can use area or perimeter.
# Print sorted area of contours
###
### YOUR CODE HERE
###
We can clearly see the jump from 2nd area to 3rd. These are the 2 inner contours.
# Remove the 2 inner contours
# Plot the rest of them
###
### YOUR CODE HERE
###
# Fit circles on coins
###
### YOUR CODE HERE
###